import { track, trigger, pauseTracking, resumeTracking } from "../effect";
import { isObject, isArray, isChange } from "../utils/components";
import { reactive } from "../reactive";
import { TrackOpTypes, TriggerOpTypes } from "./operations";

const RAW = Symbol("raw");

const arrayInstrumentations = {};

const HasOptions = ["includes", "indexOf", "lastIndexOf", "findIndex", "find"];
const ChangeOptions = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "reverse",
  "sort",
];

ChangeOptions.map((key) => {
  arrayInstrumentations[key] = function (...args) {
    pauseTracking(); // 暂停依赖收集
    const res = Array.prototype[key].apply(this, args);
    resumeTracking(); // 恢复依赖收集
    return res;
  };
});

HasOptions.map((item) => {
  arrayInstrumentations[item] = function (...args) {
    const result = Array.prototype[item].apply(this, args);

    if (!result || result < 0) {
      return Array.prototype[item].apply(this[RAW], args);
    }

    return result;
  };
});

const get = (target, key, receiver) => {
  if (key === RAW) {
    //这里用来解决数组问题,因为要在Proxy代理的对象中想办法获取到原本的数组,所以这里手动处理一个RAW座位来代表原本数组,通过this[RAW]获取
    return target;
  }
  track(target, TrackOpTypes.GET, key);
  //receiver是用来改变this的指向问题,以解决在date中调用本身的一些属性,而导致无法依赖收集
  //采用Reflect.get()方法,可以直接调用拦截器
  //针对数组的includes等方法,需要重写,因为普通调用时在Proxy中查找,无法查找到元素.所以这里策略时先在Proxy中查找一边,然后在手动在普通数组中查找一遍
  if (isArray(target) && HasOptions.includes(key)) {
    return arrayInstrumentations[key];
  }
  if (isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
    //针对数组的includes方法,需要重写,因为普通调用时在Proxy中查找,无法查找到元素.所以这里策略时先在Proxy中查找一边,然后在手动在普通数组中查找一遍
    //针对数组的push等修改方法,不需要记录...所以这里重写,终止掉数组修改时候的状态收集
    return arrayInstrumentations[key];
  }
  const result = Reflect.get(target, key, receiver);

  if (isObject(result)) {
    //如果返回值是object,则需要代理,防止出现循环引用的问题
    return reactive(result);
  }
  return result;
};

const has = (target, key) => {
  track(target, TrackOpTypes.HAS, key);
  return Reflect.has(target, key);
};

// ownKeys用于拦截for in循环
const ownKeys = (target) => {
  track(target, TrackOpTypes.ITERATE);
  return Reflect.ownKeys(target);
};

const set = (target, key, value, receiver) => {
  const Type = target.hasOwnProperty(key)
    ? TriggerOpTypes.SET
    : TriggerOpTypes.ADD;
  //区分Set类型,新增/修改都会触发set
  const oldLength = isArray(target) ? target.length : undefined;
  //这里对比length是因为数组直接修改数组,会导致部分元素被删除,这些并没有命中proxy,因为这部分删除属性属于隐性删除.所以手动对比lenght属性
  const oldValue = target[key];
  const result = Reflect.set(target, key, value, receiver);
  if (!result) {
    return result;
  }
  const newLength = isArray(target) ? target.length : undefined;
  //这里对比新旧Vulue,确保值改变,避免无用触发
  //ADD是一定修改,所以需要区分
  if (Type === TriggerOpTypes.ADD || isChange(oldValue, value)) {
    trigger(target, Type, key);
    if (isArray(target) && oldLength !== newLength) {
      if (key !== "length") {
        trigger(target, TriggerOpTypes.SET, "length");
      } else {
        for (let i = newLength; i < oldLength; i++) {
          trigger(target, TriggerOpTypes.DELETE, i);
        }
      }
    }
  }
  return result;
};

const deleteProperty = (target, key) => {
  const isHas = target.hasOwnProperty(key);
  //这里是为了防止不存在的属性被删除,导致不必要的收集器触发
  const result = Reflect.deleteProperty(target, key);
  if (isHas && result) {
    trigger(target, TriggerOpTypes.DELETE, key);
  }
  return result;
};

export default {
  get,
  has,
  set,
  ownKeys,
  deleteProperty,
};
